/*
Copyright (C) 2018-2019 de4dot@gmail.com

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

use super::super::{Code, CodeSize, Instruction, Register};
use super::enums::{FormatterFlowControl, FormatterTextKind, PrefixKind};
use super::{FormatterOptions, FormatterOutput};
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::{cmp, mem};

lazy_static! {
	static ref SPACES_TABLE: Vec<String> = create_strings(' ', 20);
}
lazy_static! {
	static ref TABS_TABLE: Vec<String> = create_strings('\t', 6);
}

fn create_strings(c: char, cap: usize) -> Vec<String> {
	let mut v = Vec::with_capacity(cap);
	let mut s = String::with_capacity(cap);
	for _ in 0..cap - 1 {
		s.push(c);
		v.push(s.clone());
	}
	s.push(c);
	assert_eq!(cap, s.len());
	v.push(s);
	v
}

pub(super) fn add_tabs(output: &mut FormatterOutput, mut column: u32, mut first_operand_char_index: u32, tab_size: u32) {
	const MAX_FIRST_OPERAND_CHAR_INDEX: u32 = 256;
	first_operand_char_index = cmp::min(first_operand_char_index, MAX_FIRST_OPERAND_CHAR_INDEX);

	if tab_size == 0 {
		let chars_left = if first_operand_char_index <= column { 1 } else { first_operand_char_index - column };
		add_strings(output, &*SPACES_TABLE, chars_left);
	} else {
		let end_col = if first_operand_char_index <= column { column + 1 } else { first_operand_char_index };
		let end_col_rounded_down = end_col / tab_size * tab_size;
		let added_tabs = end_col_rounded_down > column;
		if added_tabs {
			let tabs = (end_col_rounded_down - (column / tab_size * tab_size)) / tab_size;
			add_strings(output, &*TABS_TABLE, tabs);
			column = end_col_rounded_down;
		}
		if first_operand_char_index > column {
			add_strings(output, &*SPACES_TABLE, first_operand_char_index - column);
		} else if !added_tabs {
			add_strings(output, &*SPACES_TABLE, 1);
		}
	}
}

fn add_strings(output: &mut FormatterOutput, strings: &[String], count: u32) {
	let mut count = count as usize;
	while count > 0 {
		let n = cmp::min(count, strings.len());
		output.write(&strings[n - 1], FormatterTextKind::Text);
		count -= n;
	}
}

#[cfg_attr(has_must_use, must_use)]
#[inline]
pub(super) fn is_call(kind: FormatterFlowControl) -> bool {
	kind == FormatterFlowControl::NearCall || kind == FormatterFlowControl::FarCall
}

#[cfg_attr(has_must_use, must_use)]
pub(super) fn get_flow_control(instruction: &Instruction) -> FormatterFlowControl {
	#[cfg_attr(feature = "cargo-fmt", rustfmt::skip)]
	match instruction.code() {
		// GENERATOR-BEGIN: FormatterFlowControlSwitch
		// ⚠️This was generated by GENERATOR!🦹‍♂️
		Code::Jo_rel8_16
		| Code::Jo_rel8_32
		| Code::Jo_rel8_64
		| Code::Jno_rel8_16
		| Code::Jno_rel8_32
		| Code::Jno_rel8_64
		| Code::Jb_rel8_16
		| Code::Jb_rel8_32
		| Code::Jb_rel8_64
		| Code::Jae_rel8_16
		| Code::Jae_rel8_32
		| Code::Jae_rel8_64
		| Code::Je_rel8_16
		| Code::Je_rel8_32
		| Code::Je_rel8_64
		| Code::Jne_rel8_16
		| Code::Jne_rel8_32
		| Code::Jne_rel8_64
		| Code::Jbe_rel8_16
		| Code::Jbe_rel8_32
		| Code::Jbe_rel8_64
		| Code::Ja_rel8_16
		| Code::Ja_rel8_32
		| Code::Ja_rel8_64
		| Code::Js_rel8_16
		| Code::Js_rel8_32
		| Code::Js_rel8_64
		| Code::Jns_rel8_16
		| Code::Jns_rel8_32
		| Code::Jns_rel8_64
		| Code::Jp_rel8_16
		| Code::Jp_rel8_32
		| Code::Jp_rel8_64
		| Code::Jnp_rel8_16
		| Code::Jnp_rel8_32
		| Code::Jnp_rel8_64
		| Code::Jl_rel8_16
		| Code::Jl_rel8_32
		| Code::Jl_rel8_64
		| Code::Jge_rel8_16
		| Code::Jge_rel8_32
		| Code::Jge_rel8_64
		| Code::Jle_rel8_16
		| Code::Jle_rel8_32
		| Code::Jle_rel8_64
		| Code::Jg_rel8_16
		| Code::Jg_rel8_32
		| Code::Jg_rel8_64
		| Code::Jmp_rel8_16
		| Code::Jmp_rel8_32
		| Code::Jmp_rel8_64
		=> FormatterFlowControl::ShortBranch,
		Code::Loopne_rel8_16_CX
		| Code::Loopne_rel8_32_CX
		| Code::Loopne_rel8_16_ECX
		| Code::Loopne_rel8_32_ECX
		| Code::Loopne_rel8_64_ECX
		| Code::Loopne_rel8_16_RCX
		| Code::Loopne_rel8_64_RCX
		| Code::Loope_rel8_16_CX
		| Code::Loope_rel8_32_CX
		| Code::Loope_rel8_16_ECX
		| Code::Loope_rel8_32_ECX
		| Code::Loope_rel8_64_ECX
		| Code::Loope_rel8_16_RCX
		| Code::Loope_rel8_64_RCX
		| Code::Loop_rel8_16_CX
		| Code::Loop_rel8_32_CX
		| Code::Loop_rel8_16_ECX
		| Code::Loop_rel8_32_ECX
		| Code::Loop_rel8_64_ECX
		| Code::Loop_rel8_16_RCX
		| Code::Loop_rel8_64_RCX
		| Code::Jcxz_rel8_16
		| Code::Jcxz_rel8_32
		| Code::Jecxz_rel8_16
		| Code::Jecxz_rel8_32
		| Code::Jecxz_rel8_64
		| Code::Jrcxz_rel8_16
		| Code::Jrcxz_rel8_64
		=> FormatterFlowControl::AlwaysShortBranch,
		Code::Call_rel16
		| Code::Call_rel32_32
		| Code::Call_rel32_64
		=> FormatterFlowControl::NearCall,
		Code::Jo_rel16
		| Code::Jo_rel32_32
		| Code::Jo_rel32_64
		| Code::Jno_rel16
		| Code::Jno_rel32_32
		| Code::Jno_rel32_64
		| Code::Jb_rel16
		| Code::Jb_rel32_32
		| Code::Jb_rel32_64
		| Code::Jae_rel16
		| Code::Jae_rel32_32
		| Code::Jae_rel32_64
		| Code::Je_rel16
		| Code::Je_rel32_32
		| Code::Je_rel32_64
		| Code::Jne_rel16
		| Code::Jne_rel32_32
		| Code::Jne_rel32_64
		| Code::Jbe_rel16
		| Code::Jbe_rel32_32
		| Code::Jbe_rel32_64
		| Code::Ja_rel16
		| Code::Ja_rel32_32
		| Code::Ja_rel32_64
		| Code::Js_rel16
		| Code::Js_rel32_32
		| Code::Js_rel32_64
		| Code::Jns_rel16
		| Code::Jns_rel32_32
		| Code::Jns_rel32_64
		| Code::Jp_rel16
		| Code::Jp_rel32_32
		| Code::Jp_rel32_64
		| Code::Jnp_rel16
		| Code::Jnp_rel32_32
		| Code::Jnp_rel32_64
		| Code::Jl_rel16
		| Code::Jl_rel32_32
		| Code::Jl_rel32_64
		| Code::Jge_rel16
		| Code::Jge_rel32_32
		| Code::Jge_rel32_64
		| Code::Jle_rel16
		| Code::Jle_rel32_32
		| Code::Jle_rel32_64
		| Code::Jg_rel16
		| Code::Jg_rel32_32
		| Code::Jg_rel32_64
		| Code::Jmp_rel16
		| Code::Jmp_rel32_32
		| Code::Jmp_rel32_64
		| Code::Jmpe_disp16
		| Code::Jmpe_disp32
		=> FormatterFlowControl::NearBranch,
		Code::Call_ptr1632
		| Code::Call_ptr1616
		=> FormatterFlowControl::FarCall,
		Code::Jmp_ptr1632
		| Code::Jmp_ptr1616
		=> FormatterFlowControl::FarBranch,
		Code::Xbegin_rel16
		| Code::Xbegin_rel32
		=> FormatterFlowControl::Xbegin,
		// GENERATOR-END: FormatterFlowControlSwitch
		_ => unreachable!(),
	}
}

#[cfg_attr(has_must_use, must_use)]
pub(super) fn is_repe_or_repne_instruction(code: Code) -> bool {
	match code {
		Code::Cmpsb_m8_m8
		| Code::Cmpsw_m16_m16
		| Code::Cmpsd_m32_m32
		| Code::Cmpsq_m64_m64
		| Code::Scasb_AL_m8
		| Code::Scasw_AX_m16
		| Code::Scasd_EAX_m32
		| Code::Scasq_RAX_m64 => true,
		_ => false,
	}
}

#[cfg_attr(has_must_use, must_use)]
pub(super) fn is_rep_repe_repne_instruction(code: Code) -> bool {
	match code {
		Code::Insb_m8_DX
		| Code::Insw_m16_DX
		| Code::Insd_m32_DX
		| Code::Outsb_DX_m8
		| Code::Outsw_DX_m16
		| Code::Outsd_DX_m32
		| Code::Movsb_m8_m8
		| Code::Movsw_m16_m16
		| Code::Movsd_m32_m32
		| Code::Movsq_m64_m64
		| Code::Cmpsb_m8_m8
		| Code::Cmpsw_m16_m16
		| Code::Cmpsd_m32_m32
		| Code::Cmpsq_m64_m64
		| Code::Stosb_m8_AL
		| Code::Stosw_m16_AX
		| Code::Stosd_m32_EAX
		| Code::Stosq_m64_RAX
		| Code::Lodsb_AL_m8
		| Code::Lodsw_AX_m16
		| Code::Lodsd_EAX_m32
		| Code::Lodsq_RAX_m64
		| Code::Scasb_AL_m8
		| Code::Scasw_AX_m16
		| Code::Scasd_EAX_m32
		| Code::Scasq_RAX_m64
		| Code::Montmul_16
		| Code::Montmul_32
		| Code::Montmul_64
		| Code::Xsha1_16
		| Code::Xsha1_32
		| Code::Xsha1_64
		| Code::Xsha256_16
		| Code::Xsha256_32
		| Code::Xsha256_64
		| Code::Xstore_16
		| Code::Xstore_32
		| Code::Xstore_64
		| Code::XcryptEcb_16
		| Code::XcryptEcb_32
		| Code::XcryptEcb_64
		| Code::XcryptCbc_16
		| Code::XcryptCbc_32
		| Code::XcryptCbc_64
		| Code::XcryptCtr_16
		| Code::XcryptCtr_32
		| Code::XcryptCtr_64
		| Code::XcryptCfb_16
		| Code::XcryptCfb_32
		| Code::XcryptCfb_64
		| Code::XcryptOfb_16
		| Code::XcryptOfb_32
		| Code::XcryptOfb_64 => true,

		_ => false,
	}
}

pub(super) fn show_rep_or_repe_prefix(code: Code, options: &FormatterOptions) -> bool {
	if is_rep_repe_repne_instruction(code) {
		true
	} else {
		// We allow 'rep ret' too since some old code use it to work around an old AMD bug
		match code {
			Code::Retnw | Code::Retnd | Code::Retnq => true,
			_ => options.show_useless_prefixes(),
		}
	}
}

pub(super) fn show_repne_prefix(code: Code, options: &FormatterOptions) -> bool {
	// If it's a 'rep/repne' instruction, always show the prefix
	if is_rep_repe_repne_instruction(code) {
		true
	} else {
		options.show_useless_prefixes()
	}
}

#[cfg_attr(has_must_use, must_use)]
#[inline]
pub(super) fn is_notrack_prefix_branch(code: Code) -> bool {
	const_assert_eq!(Code::Jmp_rm32 as u32, Code::Jmp_rm16 as u32 + 1);
	const_assert_eq!(Code::Jmp_rm64 as u32, Code::Jmp_rm16 as u32 + 2);
	const_assert_eq!(Code::Call_rm32 as u32, Code::Call_rm16 as u32 + 1);
	const_assert_eq!(Code::Call_rm64 as u32, Code::Call_rm16 as u32 + 2);
	(code as u32).wrapping_sub(Code::Jmp_rm16 as u32) <= 2 || (code as u32).wrapping_sub(Code::Call_rm16 as u32) <= 2
}

#[cfg_attr(has_must_use, must_use)]
#[inline]
pub(super) fn get_segment_register_prefix_kind(register: Register) -> PrefixKind {
	debug_assert!(
		register == Register::ES
			|| register == Register::CS
			|| register == Register::SS
			|| register == Register::DS
			|| register == Register::FS
			|| register == Register::GS
	);
	const_assert_eq!(PrefixKind::CS as u32, PrefixKind::ES as u32 + 1);
	const_assert_eq!(PrefixKind::SS as u32, PrefixKind::ES as u32 + 2);
	const_assert_eq!(PrefixKind::DS as u32, PrefixKind::ES as u32 + 3);
	const_assert_eq!(PrefixKind::FS as u32, PrefixKind::ES as u32 + 4);
	const_assert_eq!(PrefixKind::GS as u32, PrefixKind::ES as u32 + 5);
	unsafe { mem::transmute(((register as u32 - Register::ES as u32) + PrefixKind::ES as u32) as u8) }
}

fn is_code64(code_size: CodeSize) -> bool {
	code_size == CodeSize::Code64 || code_size == CodeSize::Unknown
}

pub(super) fn show_segment_prefix(mut default_seg_reg: Register, instruction: &Instruction, options: &FormatterOptions) -> bool {
	let prefix_seg = instruction.segment_prefix();
	debug_assert_ne!(Register::None, prefix_seg);
	if is_code64(instruction.code_size()) {
		// ES,CS,SS,DS are ignored
		if prefix_seg == Register::FS || prefix_seg == Register::GS {
			true
		} else {
			options.show_useless_prefixes()
		}
	} else {
		if default_seg_reg == Register::None {
			default_seg_reg = get_default_segment_register(instruction);
		}
		if prefix_seg != default_seg_reg {
			true
		} else {
			options.show_useless_prefixes()
		}
	}
}

fn get_default_segment_register(instruction: &Instruction) -> Register {
	let base_reg = instruction.memory_base();
	if base_reg == Register::BP || base_reg == Register::EBP || base_reg == Register::ESP || base_reg == Register::RBP || base_reg == Register::RSP {
		Register::SS
	} else {
		Register::DS
	}
}

#[allow(unused_variables)]
pub(super) fn can_show_rounding_control(instruction: &Instruction, options: &FormatterOptions) -> bool {
	#[cfg(not(feature = "no_evex"))]
	{
		let code = instruction.code();
		if code == Code::EVEX_Vcvtsi2sd_xmm_xmm_rm32_er || code == Code::EVEX_Vcvtusi2sd_xmm_xmm_rm32_er {
			return options.show_useless_prefixes();
		}
	}
	true
}
